package main

import (
	"fmt"
	"time"
)

/*
   select 是 Go 中的一个控制结构。select 语句类似于 switch 语句，但是select会随机执行一个可运行的case。
   如果没有case可运行，它将阻塞，直到有case可运行。

   select语句的语法结构和switch语句很相似，也有case语句和default语句：

   select {
    case communication clause  :
       statement(s);
    case communication clause  :
       statement(s);
    你可以定义任意数量的 case
    default :
       statement(s);
   }

   每个case都必须是一个通信。
   所有channel表达式都会被求值。
   所有被发送的表达式都会被求值。
   如果有多个case都可以运行，select会随机公平地选出一个执行。其他不会执行。
   否则：
   	如果有default子句，则执行该语句。
	如果没有default字句，select将阻塞，直到某个通信可以运行；Go不会重新对channel或值进行求值。
*/

func main() {

	/*
	   分支语句：if，switch，select
	   select 语句类似于 switch 语句，
	       但是select会随机执行一个可运行的case。
	       如果没有case可运行，它将阻塞，直到有case可运行。
	*/

	ch01 := make(chan int)
	ch02 := make(chan int)

	go func() {
		time.Sleep(2 * time.Second)
		ch01 <- 100
	}()

	go func() {
		time.Sleep(2 * time.Second)
		ch02 <- 200
	}()

	select {
	case num01 := <-ch01:
		fmt.Println("ch01中取数据....", num01)
	case num02, ok := <-ch02:
		if ok {
			fmt.Println("ch02中取数据....", num02)
		} else {
			fmt.Println("ch02通道已经关闭....")
		}
	}

	ch03 := make(chan int)
	ch04 := make(chan int)

	select {
	case <-ch03:
		fmt.Println("case1可以执行....")
	case <-ch04:
		fmt.Println("case2可以执行....")
	case <-time.After(3 * time.Second):
		fmt.Println("case3执行....timeout....")
		//default:
		//	fmt.Println("执行了default....")
	}

	fmt.Println("start : go select使用，不阻塞在channel上")

	//var wg sync.WaitGroup
	//c := make(chan int, 3)
	//wg.Add(2)
	//go func() {
	//	producer(c)
	//	wg.Done()
	//}()
	//
	//go func() {
	//	consumer(c)
	//	wg.Done()
	//}()
	//
	//wg.Wait()
	fmt.Println("end: go select使用，不阻塞在channel上")

	fmt.Println("start: go select --->nil channel 的妙用")
	// 不符合预期
	basicLogic()
	// 符合预期
	nilChannelLogic()
	fmt.Println("end: go select --->nil channel 的妙用")
}

func producer(c chan<- int) {
	var i int = 1
	for {
		time.Sleep(2 * time.Second)
		ok := trySend(c, i)
		if ok {
			fmt.Printf("[producer]: send [%d] to channel\n", i)
			i++
			continue
		}
		fmt.Printf("[producer]: try send [%d], but channel is full\n", i)
	}
}

// 无论是无缓冲 channel 还是带缓冲 channel，这两个函数都能适用，并且不会阻塞在空 channel 或元素个数已经达到容量的 channel 上
func tryRecv(c <-chan int) (int, bool) {
	select {
	case i := <-c:
		return i, true

	default:
		return 0, false
	}
}

// 无论是无缓冲 channel 还是带缓冲 channel，这两个函数都能适用，并且不会阻塞在空 channel 或元素个数已经达到容量的 channel 上
func trySend(c chan<- int, i int) bool {
	select {
	case c <- i:
		return true
	default:
		return false
	}
}

func consumer(c <-chan int) {
	for {
		i, ok := tryRecv(c)
		if !ok {
			fmt.Println("[consumer]: try to recv from channel, but the channel is empty")
			time.Sleep(1 * time.Second)
			continue
		}
		fmt.Printf("[consumer]: recv [%d] from channel\n", i)
		if i >= 3 {
			fmt.Println("[consumer]: exit")
			return
		}
	}
}

// 期望程序在接收完 ch1 和 ch2 两个 channel 上的数据后就退出
// 实际运行的输出结果却是在输出 5 之后，程序输出了许多的 0 值，之后才输出 10 并退出
func basicLogic() {
	ch01, ch02 := make(chan int), make(chan int)
	go func() {
		time.Sleep(5 * time.Second)
		ch01 <- 5
		close(ch01)
	}()

	go func() {
		time.Sleep(5 * time.Second)
		ch02 <- 10
		close(ch02)
	}()

	var ok01, ok02 bool

	// 由于 ch01 被关闭，从一个已关闭的 channel 接收数据将永远不会被阻塞，于是新一轮 select 又把case x := <-ch01这个分支选出并执行。
	// 由于 ch01 处于关闭状态，从这个 channel 获取数据，我们会得到这个 channel 对应类型的零值，这里就是 0
	for {
		select {
		case x := <-ch01:
			ok01 = true
			fmt.Println(x)
		case y := <-ch02:
			ok02 = true
			fmt.Println(y)
		}
		if ok01 && ok02 {
			break
		}
	}
	fmt.Println("program end")
}

// 改进后的示例程序的最关键的一个变化，就是在判断 ch01 或 ch01 被关闭后，显式地将 ch01 或 ch02 置为 nil。
func nilChannelLogic() {
	ch01, ch02 := make(chan int), make(chan int)
	go func() {
		time.Sleep(5 * time.Second)
		ch01 <- 5
		close(ch01)
	}()

	go func() {
		time.Sleep(5 * time.Second)
		ch02 <- 10
		close(ch02)
	}()

	for {
		select {
		case x, ok := <-ch01:
			if !ok {
				ch01 = nil
			} else {
				fmt.Println(x)
			}
		case y, ok := <-ch02:
			if !ok {
				ch02 = nil
			} else {
				fmt.Println(y)
			}
		}
		if ch01 == nil && ch02 == nil {
			break
		}
	}
	fmt.Println("program end")
}
